package com.marsoft.test.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.marsoft.test.constant.GuarantyConstant;
import com.marsoft.test.entity.*;
import com.marsoft.test.exception.DefinitionException;
import com.marsoft.test.mapper.GuarantyContractMapper;
import com.marsoft.test.service.MaximumAmountService;
import com.marsoft.test.utils.RandomNumberUtils;
import org.apache.commons.lang.StringUtils;
import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.PropertySource;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import static com.mysql.cj.conf.PropertyKey.logger;

@Service
@Transactional
public class MaximumAmountServiceImpl implements MaximumAmountService {

    @Autowired
    GuarantyContractMapper guarantyContractMapper;

    private Logger logger = LoggerFactory.getLogger(getClass());

    @Autowired
    StringRedisTemplate redisTemplate;

    @Autowired
    RedissonClient redissonClient;

    @Override
    public List<MaximumAmountServiceOutEntity> search(MaximumAmountControllerEntity maximumAmountControllerEntity) {

        //分页起始码以及每页页数
        PageHelper.startPage(maximumAmountControllerEntity.getPage(), GuarantyConstant.PAGE_SIZE);

        MaximumAmountServiceInEntity amountServiceInEntity = new MaximumAmountServiceInEntity();

        BeanUtils.copyProperties(maximumAmountControllerEntity, amountServiceInEntity);

        // 只查询未被逻辑删除的字段
        amountServiceInEntity.setLogicDelete(GuarantyConstant.LOGIC_NOT_DELETE);

        // 从缓存中获取最高额列表数据
        String maximumAmountList = redisTemplate.opsForValue().get(GuarantyConstant.MAXIMUM_AMOUNT_LIST_KEY);

        List<MaximumAmountDaoEntity> amountDaoEntityList = null;

        //如果缓存中有数据就用缓存中的
        if (StringUtils.isNotEmpty(maximumAmountList)) {

            // 将redis中的json转为List<MaximumAmountDaoEntity>
            amountDaoEntityList = JSON.
                    parseObject(maximumAmountList, new TypeReference<List<MaximumAmountDaoEntity>>() {
                    });

            if (amountDaoEntityList == null) {
                throw new DefinitionException("缓存失效，请重新再试");
            }

        } else {

            amountDaoEntityList = getMaximumAmountRedissionLock(amountServiceInEntity, amountDaoEntityList);
        }

        List<MaximumAmountServiceOutEntity> amountServiceOutEntityList1 = new ArrayList<>();

        List<MaximumAmountServiceOutEntity> amountServiceOutEntityList = amountDaoEntityList.stream().map(entity -> {
            MaximumAmountServiceOutEntity amountServiceOutEntityListeOutEntity = new MaximumAmountServiceOutEntity();
            BeanUtils.copyProperties(entity, amountServiceOutEntityListeOutEntity);
            amountServiceOutEntityList1.add(amountServiceOutEntityListeOutEntity);
            return amountServiceOutEntityListeOutEntity;
        }).collect(Collectors.toList());

        return new PageInfo(amountServiceOutEntityList1).getList();
    }

    /**
     * 用redis实现分布式锁
     *
     * @param amountServiceInEntity db查询条件
     * @param amountDaoEntityList   空的最高额和合同列表
     * @return List<MaximumAmountDaoEntity> 查询出的最高额和合同列表
     */
    private List<MaximumAmountDaoEntity> getMaximumAmountRedisLock(MaximumAmountServiceInEntity amountServiceInEntity, List<MaximumAmountDaoEntity> amountDaoEntityList) {

        // 从缓存中获取最高额列表数据
        String maximumAmountList = redisTemplate.opsForValue().get(GuarantyConstant.MAXIMUM_AMOUNT_LIST_KEY);

        //如果缓存中有数据就用缓存中的
        if (StringUtils.isNotEmpty(maximumAmountList)) {

            // 将redis中的json转为List<MaximumAmountDaoEntity>
            amountDaoEntityList = JSON.
                    parseObject(maximumAmountList, new TypeReference<List<MaximumAmountDaoEntity>>() {
                    });

            if (amountDaoEntityList == null) {
                throw new DefinitionException("缓存失效，请重新再试");
            }

            return amountDaoEntityList;
        }

        // 作为lock的value值，用于区分是哪个请求加的锁
        String uuid = UUID.randomUUID().toString();

        // 设置过期时间，必须和加锁是同步的，原子的
        Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", uuid, 300, TimeUnit.SECONDS);

        if (lock == null) {
            lock = false;
        }

        //加锁成功... 执行业务
        if (lock) {

            try {

                // 否则去数据库中查询，再放入缓存
                amountDaoEntityList = guarantyContractMapper.selectMaximumAmountList(amountServiceInEntity);

                // 设置一天的过期时间
                redisTemplate.opsForValue().set(
                        GuarantyConstant.MAXIMUM_AMOUNT_LIST_KEY, JSON.toJSONString(amountDaoEntityList), 1, TimeUnit.DAYS);

            } finally {

//                下面的操作相当于这个的原子操作
//                String lock2 = redisTemplate.opsForValue().get("lock");
//                if (lock2.equals(uuid))
//                  redisTemplate.delete("lock");

                // lua脚本实习操作的原子性
                String script = "if redis.call('get', KEYS[1]) == ARGV[1] then " +
                        "return redis.call('del', KEYS[1]) else return 0 end ";

                //删除锁
                //KEYS[1]==Arrays.asList("lock"),ARGV[1]==uuid
                Long flag = redisTemplate.execute(new
                                DefaultRedisScript<Long>(script, Long.class)
                        , Arrays.asList("lock"), uuid);

                if (flag == null || flag == 0) {

                    logger.info("锁已失效");
                }
            }
        } else {

            //加锁失败...休眠 100ms 重试
            try {

                Thread.sleep(100);
                getMaximumAmountRedisLock(amountServiceInEntity, amountDaoEntityList);

            } catch (InterruptedException e) {
                logger.error("中断异常");
                e.printStackTrace();
            }

        }
        return amountDaoEntityList;
    }

    /**
     * 用Redission实现分布式锁
     *
     * @param amountServiceInEntity db查询条件
     * @param amountDaoEntityList   空的最高额和合同列表
     * @return List<MaximumAmountDaoEntity> 查询出的最高额和合同列表
     */
    private List<MaximumAmountDaoEntity> getMaximumAmountRedissionLock(MaximumAmountServiceInEntity amountServiceInEntity, List<MaximumAmountDaoEntity> amountDaoEntityList) {

        // 从缓存中获取最高额列表数据
        String maximumAmountList = redisTemplate.opsForValue().get(GuarantyConstant.MAXIMUM_AMOUNT_LIST_KEY);

        //如果缓存中有数据就用缓存中的
        if (StringUtils.isNotEmpty(maximumAmountList)) {

            // 将redis中的json转为List<MaximumAmountDaoEntity>
            amountDaoEntityList = JSON.
                    parseObject(maximumAmountList, new TypeReference<List<MaximumAmountDaoEntity>>() {
                    });

            if (amountDaoEntityList == null) {
                throw new DefinitionException("缓存失效，请重新再试");
            }

            return amountDaoEntityList;
        }

        RLock myLock = redissonClient.getLock("myMaximumAmountLock");

        //加锁
        myLock.lock();
        try {

            // 否则去数据库中查询，再放入缓存
            amountDaoEntityList = guarantyContractMapper.selectMaximumAmountList(amountServiceInEntity);

            // 设置一天的过期时间
            redisTemplate.opsForValue().set(
                    GuarantyConstant.MAXIMUM_AMOUNT_LIST_KEY, JSON.toJSONString(amountDaoEntityList), 1, TimeUnit.DAYS);

        } finally {

            myLock.unlock();
        }

        return amountDaoEntityList;
    }

    @Override
    public void saveMaximumGuaranty(MaximumAmountControllerEntity maximumAmountControllerEntity) {

        MaximumAmountServiceInEntity amountServiceInEntity = new MaximumAmountServiceInEntity();

        BeanUtils.copyProperties(maximumAmountControllerEntity, amountServiceInEntity);

        // 随机担保合同号
        amountServiceInEntity.setGuarantyContractNo(RandomNumberUtils.randomNumber());

        amountServiceInEntity.setLogicDelete(GuarantyConstant.LOGIC_NOT_DELETE);

        // 因为合同号为主键并且是不重复的所以不用担心主键冲突
        guarantyContractMapper.saveMaximumAmount(amountServiceInEntity);

        // 将redis中的缓存数据删除，下一次获取列表时重新放入缓存
        redisTemplate.delete(GuarantyConstant.MAXIMUM_AMOUNT_LIST_KEY);
    }

    @Override
    public int daleteMaximumGuaranty(String guarantyContractNo) {

        MaximumAmountServiceInEntity amountServiceInEntity = new MaximumAmountServiceInEntity();

        amountServiceInEntity.setGuarantyContractNo(guarantyContractNo);

        amountServiceInEntity.setLogicDelete(GuarantyConstant.LOGIC_DELETE);

        return guarantyContractMapper.deleteMaximumAmount(amountServiceInEntity);
    }

    @Override
    public void saveCollateralInfo(MaximumAmountGuarantyInfoEntity amountGuarantyInfoEntity) {


        MaximumAmountGuarantyInfoServiceEntity amountServiceInEntity = new MaximumAmountGuarantyInfoServiceEntity();

        BeanUtils.copyProperties(amountGuarantyInfoEntity, amountServiceInEntity);

        if (amountServiceInEntity.getGuarantyContractNo() == null) {

            throw new DefinitionException("担保合同号被篡改");
        }

        // 根据担保合同号查询出担保合同编号
        Integer contractNo = guarantyContractMapper.selectContractNo(amountServiceInEntity.getGuarantyContractNo());

        amountServiceInEntity.setContractNo(contractNo);

        amountServiceInEntity.setLogicDelete(GuarantyConstant.LOGIC_NOT_DELETE);

        guarantyContractMapper.saveCollateralInfo(amountServiceInEntity);

    }

}
